/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package coisa.comm;

import gnu.io.CommPort;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Usage:
 * 1. override methods
 * 2. construct, initialize and set delay
 * 3. run()
 * 4. stop()
 * @author marcos
 */
public abstract class Poller implements Runnable {

    static public abstract class Reader extends Poller {

        public Reader(CommPort commPort, List messageSeparators) throws IOException {
            super(commPort, messageSeparators);
        }

        public Reader(CommPort commPort, int sizeBufIn, int sizeBufOut, List messageSeparators) throws IOException {
            super(commPort, sizeBufIn, sizeBufOut, messageSeparators);
        }

        public Reader(InputStream in, OutputStream out, List messageSeparators) {
            super(in, out, messageSeparators);
        }

        public Reader() {
        }

        @Override
        public byte[] getQuery() {
            return null;
        }

        @Override
        public void query() {
        }
    }
    
    private BufferedInputStream in;
    private BufferedOutputStream out;
    private List separators;
    private boolean stopping = false;
    private ArrayList<Byte> splitting = new ArrayList<>();
    private long delay = 25;

    public final void init(BufferedInputStream in, BufferedOutputStream out, List messageSeparators) {
        this.in = in;
        this.out = out;
        this.separators = messageSeparators;
    }

    public final void init(InputStream in, OutputStream out, List messageSeparators) {
        init(new BufferedInputStream(in), new BufferedOutputStream(out), messageSeparators);
    }

    public void Poller(BufferedInputStream in, BufferedOutputStream out, List messageSeparators) {
        init(in, out, messageSeparators);
    }

    public Poller(InputStream in, OutputStream out, List messageSeparators) {
        init(in, out, messageSeparators);
    }

    public Poller(CommPort commPort, int sizeBufIn, int sizeBufOut, List messageSeparators) throws IOException {
        init(new BufferedInputStream(commPort.getInputStream(), sizeBufIn),
                new BufferedOutputStream(commPort.getOutputStream(), sizeBufOut),
                messageSeparators);
    }

    public Poller(CommPort commPort, List messageSeparators) throws IOException {
        init(commPort.getInputStream(), commPort.getOutputStream(), messageSeparators);
    }
    
    public Poller() {
    }

    /**
     * Try to read as many raw data as available.
     *
     * @return
     */
    protected int read() {
        try {
            if(in.available() <= 0) {
                return 0;
            }
            byte[] buf = new byte[in.available()];
            if (buf.length <= 0) {
                return 0;
            }
            int read = in.read(buf);
            if (read > 0) {
                split(buf, read);
            }
            return read;
        } catch (IOException ex) {
            Logger.getLogger(Poller.class.getName()).log(Level.SEVERE, null, ex);
            return -1;
        }
    }

    /**
     * Get all the data read and try to split into messages.
     *
     * @param buf
     * @param read
     */
    protected void split(byte[] buf, int read) {
        for (int i = 0; i < read; ++i) {
            splitting.add(buf[i]);
            if (separators.contains(buf[i])) {
                gotResponse(splitting); // hit!
                splitting.clear();
            }
        }
    }

    /**
     * Effectively makes the query.
     */
    protected void query() {
        try {
            byte[] query = getQuery();
            if (query == null) {
                return;
            }
            out.write(query);
        } catch (IOException ex) {
            Logger.getLogger(Poller.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Query as soon as got response. For different behavior, override this
     * method.
     *
     * @param response
     */
    protected void gotResponse(ArrayList<Byte> response) {
        parseResponse(response);
        query();
    }

    public abstract void parseResponse(ArrayList<Byte> response);

    public abstract byte[] getQuery();

    /**
     * @return true if interrupted during the delay.
     */
    private void delay() {
        try {
            Thread.sleep(getDelay());
        } catch (InterruptedException ex) {
            Logger.getLogger(Poller.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Default policy on polling is to delay when could not read any raw data.
     * Override this method if is wanted another behavior.
     *
     * @param bytesRead
     */
    protected boolean continueReading(int bytesRead) {
        if (bytesRead <= 0) {
            if (isStopping()) {
                return false;
            }
            delay();
        }
        return !isStopping();
    }

    @Override
    public void run() {
        beginPoll();
        while (continueReading(read()));
        endPoll();
    }

    /**
     * This is not intended to be called directly, this is called on beginning
     * of run(). Override for other than this default policy. It is important to
     * set stopping false.
     */
    protected void beginPoll() {
        setStoppingFalse();
        query();
    }
    
    /**
     * This is not intended to be called directly, this is called on end
     * of run(). Override for other than this default policy. 
     */
    protected void endPoll() {
    }

    synchronized protected void setStoppingFalse() {
        stopping = false;
    }

    synchronized public void stop() {
        stopping = true;
    }

    synchronized public boolean isStopping() {
        return stopping;
    }

    public long getDelay() {
        return delay;
    }

    public void setDelay(long delay) {
        this.delay = delay;
    }

    public List getSeparators() {
        return separators;
    }

    public void setSeparators(List separators) {
        this.separators = separators;
    }
}
